using System;
using System.Net;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;


namespace FTPClient
{

	public class FtpClient : IDisposable
	{

		public class FtpException : Exception
		{
			public FtpException(string message) : base(message){}
			public FtpException(string message, Exception innerException) : base(message,innerException){}
		}

		private static int BUFFER_SIZE = 8192;
        private static Encoding ASCII = Encoding.GetEncoding(1252);//Encoding.UTF8;
        
		private bool verboseDebugging = false;

		// defaults
		private string server = "localhost";
        private string remoteIP = "127.0.0.1";
		private string remotePath = ".";
		private string username = "anonymous";
		private string password = "anonymous@anonymous.net";
		private string message = null;
		private string result = null;
        private string sServerType = "Unknown";

        private bool MDTM = false;
        private bool REST = false;
        private bool SIZE = false;
        private bool MLST = false;
        private bool MLSD = false;
        private bool CLNT = false;
        private bool MFMT = false;
        private bool OPTS = false;

        public string MLST_Format = "type";

		private int port = 21;
		private int bytes = 0;
		private int resultCode = 0;

        public long offset = 0;

		public bool loggedin = false;
		private bool binMode = true;

		private Byte[] buffer = new Byte[BUFFER_SIZE];
		private Socket clientSocket = null;

		private int timeoutSeconds = 10;
        private Int64 iProgress = 0;
        public Int64 iBytesDownloaded = 0;

        private bool bCancelFiletransfer = false;

        // Declare the delegate (if using non-generic pattern).
        public delegate void RespondEventHandler(object sender, RespondEventArgs e);

        // Declare the event.
        public event RespondEventHandler Respond;

        // Wrap the event in a protected virtual method
        // to enable derived classes to raise the event.
        public virtual void RaiseRespondEvent()
        {
            // Raise the event by using the () operator.
            Respond(this, new RespondEventArgs(message));
        }

        // Declare the delegate (if using non-generic pattern).
        public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);

        // Declare the event.
        public event ProgressChangedEventHandler ProgressChanged;

        // Wrap the event in a protected virtual method
        // to enable derived classes to raise the event.
        public virtual void RaiseProgressChangedEvent()
        {
            // Raise the event by using the () operator.
            ProgressChanged(this, new ProgressChangedEventArgs((int)iProgress, iBytesDownloaded));
        }

		/// <summary>
		/// Default contructor
		/// </summary>
		public FtpClient()
		{
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="server"></param>
		/// <param name="username"></param>
		/// <param name="password"></param>
		public  FtpClient(string server, string username, string password)
		{
			this.server = server;
			this.username = username;
			this.password = password;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="server"></param>
		/// <param name="username"></param>
		/// <param name="password"></param>
		/// <param name="timeoutSeconds"></param>
		/// <param name="port"></param>
		public FtpClient(string server, string username, string password, int timeoutSeconds, int port)
		{
			this.server = server;
			this.username = username;
			this.password = password;
			this.timeoutSeconds = timeoutSeconds;
			this.port = port;
		}

		/// <summary>
		/// Display all communications to the debug log
		/// </summary>
		public bool VerboseDebugging
		{
			get
			{
				return this.verboseDebugging;
			}
			set
			{
				this.verboseDebugging = value;
			}
		}
		/// <summary>
		/// Remote server port. Typically TCP 21
		/// </summary>
		public int Port
		{
			get
			{
				return this.port;
			}
			set
			{
				this.port = value;
			}
		}
		/// <summary>
		/// Timeout waiting for a response from server, in seconds.
		/// </summary>
		public int Timeout
		{
			get
			{
				return this.timeoutSeconds;
			}
			set
			{
				this.timeoutSeconds = value;
			}
		}
		/// <summary>
		/// Gets and Sets the name of the FTP server.
		/// </summary>
		/// <returns></returns>
		public string Server
		{
			get
			{
				return this.server;
			}
			set
			{
				this.server = value;
			}
		}

        public string RemoteIP
        {
            get
            {
                return this.remoteIP;
            }
        }

        public string ServerType
        {
            get
            {
                return this.sServerType;
            }
            set
            {
                this.sServerType = value;
            }
        }

		/// <summary>
		/// Gets and Sets the port number.
		/// </summary>
		/// <returns></returns>
		public int RemotePort
		{
			get
			{
				return this.port;
			}
			set
			{
				this.port = value;
			}
		}
		/// <summary>
		/// GetS and Sets the remote directory.
		/// </summary>
		public string RemotePath
		{
			get
			{
				return this.remotePath;
			}
			set
			{
				this.remotePath = value;
			}

		}
		/// <summary>
		/// Gets and Sets the username.
		/// </summary>
		public string Username
		{
			get
			{
				return this.username;
			}
			set
			{
				this.username = value;
			}
		}
		/// <summary>
		/// Gets and Set the password.
		/// </summary>
		public string Password
		{
			get
			{
				return this.password;
			}
			set
			{
				this.password = value;
			}
		}

        public Encoding Encoding
        {
            get
            {
                return ASCII;
            }
            set
            {
                ASCII = value;
            }
        }

        public bool CancelFiletransfer
        {
            set { bCancelFiletransfer = value; }
            get { return bCancelFiletransfer; }
        }

		/// <summary>
		/// If the value of mode is true, set binary mode for downloads, else, Ascii mode.
		/// </summary>
		public bool BinaryMode
		{
			get
			{
				return this.binMode;
			}
			set
			{
				//if ( this.binMode == value ) return;

                this.binMode = value;

				if ( value )
					sendCommand("TYPE I");
				else
					sendCommand("TYPE A");

                //this.readResponse();

				if ( this.resultCode != 200 ) throw new FtpException(result.Substring(4));
			}
		}
		/// <summary>
		/// Login to the remote server.
		/// </summary>
		public void Login()
		{
			if ( this.loggedin ) this.Close();
            
			Debug.WriteLine("Opening connection to " + this.server, "FtpClient" );

			IPAddress addr = null;
			IPEndPoint ep = null;

			try
			{
                //this.clientSocket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
				// addr = Dns.Resolve(this.server).AddressList[0];
                //addr = Dns.GetHostEntry(this.server).AddressList[0];

                IPAddress[] ipv4Addresses = Array.FindAll(Dns.GetHostEntry(this.server).AddressList, a => a.AddressFamily == AddressFamily.InterNetwork);

                if(ipv4Addresses.Length == 0)
                {
                    if(!Socket.OSSupportsIPv6)
                    {
                        this.message = "Your system does not support IPv6! Check you have IPv6 enabled!";

                        RaiseRespondEvent();

                        throw new FtpException("Your system does not support IPv6! Check you have IPv6 enabled!", null);
                    }

                    IPHostEntry ipHE = Dns.GetHostEntry(this.server);

                    if(ipHE.AddressList.Length == 0)
                    {
                        this.message = String.Format("The IP address for {0} could not be resolved!", this.server);

                        RaiseRespondEvent();

                        throw new FtpException(String.Format("The IP address for {0} could not be resolved!", this.server), null);
                    }
                    else
                        addr = Dns.GetHostEntry(this.server).AddressList[0];
                }
                else
                    addr = ipv4Addresses[0];

                
                this.clientSocket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                ep = new IPEndPoint(addr, this.port);

                this.message = String.Format("Opening connection to host: {0} on port: {1}", addr.ToString(), this.port);

                RaiseRespondEvent();

                this.remoteIP = addr.ToString();
                this.clientSocket.Ttl = 42;

                this.clientSocket.ReceiveBufferSize = 8192;
                this.clientSocket.SendBufferSize = 8192;

                this.clientSocket.NoDelay = true;

                this.clientSocket.ReceiveTimeout = this.timeoutSeconds * 1000;
                this.clientSocket.SendTimeout = this.timeoutSeconds * 1000;

				this.clientSocket.Connect(ep);

                this.readResponse();
			}
			catch(Exception ex)
			{
				// doubtfull
				if ( this.clientSocket != null && this.clientSocket.Connected ) this.clientSocket.Close();

				throw new FtpException("Couldn't connect to remote server",ex);
			}
			
			if(this.resultCode != 220)
			{
				this.Close();
				throw new FtpException(this.result);
			}

			this.sendCommand( "USER " + username );

			if( !(this.resultCode == 331 || this.resultCode == 230) )
			{
				this.cleanup();
				throw new FtpException(this.result);
			}

			if( this.resultCode != 230 )
			{
				this.sendCommand( "PASS " + password );

				if( !(this.resultCode == 230 || this.resultCode == 202) )
				{
					this.cleanup();
					throw new FtpException(this.result);
				}
			}

			this.loggedin = true;

			Debug.WriteLine( "Connected to " + this.server, "FtpClient" );

            getServerInfo();

            this.BinaryMode = true;
                        
			this.ChangeDir(this.remotePath);
		}

        private void getServerInfo()
        {
            this.sendCommand("SYST");

            if (this.resultCode == 215)
                this.sServerType = this.result.Substring(this.result.IndexOf(" ") + 1);

            this.sendCommand("FEAT");

            if (this.resultCode == 211)
            { 
                /*211-Extensions supported
                Antwort:	 UTF8
                Antwort:	 OPTS MODE;MLST;UTF8
                Antwort:	 CLNT
                Antwort:	 CSID Name; Version;
                Antwort:	 HOST domain
                Antwort:	 SITE PSWD;SET;ZONE;CHMOD;MSG;EXEC;HELP
                Antwort:	 AUTH TLS;SSL;TLS-C;TLS-P;
                Antwort:	 PBSZ
                Antwort:	 PROT
                Antwort:	 CCC
                Antwort:	 SSCN
                Antwort:	 RMDA directoryname
                Antwort:	 DSIZ
                Antwort:	 AVBL
                Antwort:	 MODE Z
                Antwort:	 THMB BMP|JPEG|GIF|TIFF|PNG max_width max_height pathname
                Antwort:	 REST STREAM
                Antwort:	 SIZE
                Antwort:	 MDTM
                Antwort:	 MDTM YYYYMMDDHHMMSS[+-TZ];filename
                Antwort:	 MFMT
                Antwort:	 MFCT
                Antwort:	 MFF Create;Modify;
                Antwort:	 XCRC filename;start;end
                Antwort:	 MLST Type*;Size*;Create;Modify*;Perm;Win32.ea;Win32.dt;Win32.dl
                Antwort:	211 End (for details use "HELP commmand" where command is the command of interest)
                */

                string[] sFeatures = this.message.Split(new string[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);

                foreach (string sFeature in sFeatures)
                {
                    string[] a = sFeature.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    switch (a[0])
                    {
                        case "MDTM":
                            this.MDTM = true;
                            break;
                        case "REST":
                            this.REST = true;
                            break;
                        case "SIZE":
                            this.SIZE = true;
                            break;
                        case "MLST":
                            MLST = true;
                            MLSD = true;
                            MLST_Format = a[1].Substring(0, a[1].IndexOf('*'));
                            break;
                        case "MLSD":
                            this.MLSD = true;
                            break;
                        case "UTF8":
                            ASCII = Encoding.UTF8;
                            break;
                        case "CLNT":
                            CLNT = true;
                            break;
                        case "MFMT":
                            this.MFMT = true;
                            break;
                        case "OPTS":
                            this.OPTS = true;
                            break;
                        default:
                            break;
                    }
                }

                if (CLNT)
                    sendCommand("CLNT eeZeeFTP");

                sendCommand("OPTS UTF8 ON");

                if (this.resultCode == 200)
                    ASCII = Encoding.UTF8;

                if(MLST)
                    sendCommand("OPTS MLST type;size;modify;perm;");
            }
        }
		
		/// <summary>
		/// Close the FTP connection.
		/// </summary>
		public void Close()
		{
			Debug.WriteLine("Closing connection to " + this.server, "FtpClient" );
            
			if( this.clientSocket != null )
			{
				this.sendCommand("QUIT");
			}

            this.cleanup();
		}

		/// <summary>
		/// Return a string array containing the remote directory's file list.
		/// </summary>
		/// <returns></returns>
		public string[] GetFileList()
		{
			return this.GetFileList("");
		}

		/// <summary>
		/// Return a string array containing the remote directory's file list.
		/// </summary>
		/// <param name="mask"></param>
		/// <returns></returns>
		public string[] GetFileList(string mask)
		{
			if ( !this.loggedin ) this.Login();

			Socket cSocket = createDataSocket();

            string sCommand = "LIST";

            if (this.MLSD && mask == "")
                sCommand = "MLSD";

            if (mask.Length > 0)
                sCommand += " " + mask;

			this.sendCommand(sCommand);

			if(!(this.resultCode == 150 || this.resultCode == 125 || this.resultCode == 226)) 
                return new string[0];    
                
            //throw new FtpException(this.result.Substring(4));

			this.message = "";

			DateTime timeout = DateTime.Now.AddSeconds(this.timeoutSeconds);

            int bytes = cSocket.Receive(buffer, buffer.Length, 0);
            while (bytes > 0)
			{
				this.message += ASCII.GetString(buffer, 0, bytes);

                bytes = cSocket.Receive(buffer, buffer.Length, 0);
				if ( bytes <= 0 ) break;
			}

			string[] msg = this.message.Replace("\r","").Split('\n');

			cSocket.Close();

			if ( this.message.IndexOf( "No such file or directory" ) != -1 )
				msg = new string[]{};

            if (this.resultCode != 226)
            {
                this.readResponse();

                if (this.resultCode != 226)
                    msg = new string[] { };
            }
			//	throw new FtpException(result.Substring(4));

			return msg;
		}
		
		/// <summary>
		/// Return the size of a file.
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public long GetFileSize(string fileName)
		{
			if ( !this.loggedin ) this.Login();

			this.sendCommand("SIZE " + fileName);

            if (this.resultCode == 213)
                return long.Parse(this.result.Substring(4));

            if (this.resultCode == 550)
                return -2;

			return -1;
		}
		
		/// <summary>
		/// Download a file to the Assembly's local directory,
		/// keeping the same file name.
		/// </summary>
		/// <param name="remFileName"></param>
        public void Download(string remFileName, Int64 iFileSize)
		{
			this.Download(remFileName,"",false, iFileSize);
		}

        public void Download(string remFileName)
        {
            this.Download(remFileName, "", false,0);
        }

		/// <summary>
		/// Download a remote file to the Assembly's local directory,
		/// keeping the same file name, and set the resume flag.
		/// </summary>
		/// <param name="remFileName"></param>
		/// <param name="resume"></param>
        public void Download(string remFileName, Boolean resume)
		{
			this.Download(remFileName,"",resume, 0);
		}

        public void Download(string remFileName, Boolean resume, Int64 iFileSize)
        {
            this.Download(remFileName, "", resume, iFileSize);
        }
		
		/// <summary>
		/// Download a remote file to a local file name which can include
		/// a path. The local file name will be created or overwritten,
		/// but the path must exist.
		/// </summary>
		/// <param name="remFileName"></param>
		/// <param name="locFileName"></param>
        public void Download(string remFileName, string locFileName)
		{
			this.Download(remFileName,locFileName,false,0);
		}

        public void Download(string remFileName, string locFileName, Int64 iFileSize)
        {
            this.Download(remFileName, locFileName, false, iFileSize);
        }

		/// <summary>
		/// Download a remote file to a local file name which can include
		/// a path, and set the resume flag. The local file name will be
		/// created or overwritten, but the path must exist.
		/// </summary>
		/// <param name="remFileName"></param>
		/// <param name="locFileName"></param>
		/// <param name="resume"></param>
		public void Download(string remFileName,string locFileName,Boolean resume,Int64 iFileSize)
		{
            FileStream output = null;

            try
            {
                if (!this.loggedin) this.Login();

                //this.BinaryMode = true;

                this.iProgress = 0;

                CancelFiletransfer = false;

                Debug.WriteLine("Downloading file " + remFileName + " from " + server + "/" + remotePath, "FtpClient");

                if (locFileName.Equals(""))
                {
                    locFileName = remFileName;
                }

                if (!File.Exists(locFileName))
                {
                    output = File.Create(locFileName);

                    output.Close();
                }
                    
                output = new FileStream(locFileName, FileMode.Open);

                Socket cSocket = createDataSocket();

                offset = 0;

                if (resume)
                {
                    offset = output.Length;

                    if (offset > 0)
                    {
                        this.sendCommand("REST " + offset);
                        if (this.resultCode != 350)
                        {
                            //Server dosnt support resuming
                            offset = 0;
                            Debug.WriteLine("Resuming not supported:" + result.Substring(4), "FtpClient");
                        }
                        else
                        {
                            Debug.WriteLine("Resuming at offset " + offset, "FtpClient");
                            output.Seek(offset, SeekOrigin.Begin);
                        }
                    }
                }

                this.sendCommand("RETR " + remFileName);

                if (this.resultCode != 150 && this.resultCode != 125 && this.resultCode != 226)
                {
                    output.Close();
                    throw new FtpException(this.result.Substring(4));
                }

                DateTime timeout = DateTime.Now.AddSeconds(this.timeoutSeconds);

                int bytes = cSocket.Receive(buffer, buffer.Length, 0);
                iBytesDownloaded = offset + bytes;
                
                DateTime dtStart = DateTime.Now;

                while (bytes > 0)
                {
                    if (CancelFiletransfer)
                    {
                        cSocket.Close();

                        output.Close();

                        return;
                    }

                    output.Write(this.buffer, 0, bytes);

                    bytes = cSocket.Receive(buffer, buffer.Length, 0);

                    iBytesDownloaded += bytes;
                }

                output.Close();

                if (cSocket.Connected) cSocket.Close();

                if (this.resultCode != 226)
                {
                    this.readResponse();
                }

                if(new FileInfo(locFileName).Length == 0)
                {
                    File.Delete(locFileName);
                    throw new FtpException("Datei nicht gefunden.");
                }

                if (this.resultCode != 226 && this.resultCode != 250)
                    throw new FtpException(this.result.Substring(4));
            }
            catch (Exception ex)
            {
                if (output != null)
                    output.Close();

                throw new FtpException(ex.Message);
            }
		}

		
		/// <summary>
		/// Upload a file.
		/// </summary>
		/// <param name="fileName"></param>
        public void Upload(string fileName, long lFileSize)
		{
			this.Upload(fileName,false, lFileSize);
		}

		
		/// <summary>
		/// Upload a file and set the resume flag.
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="resume"></param>
		public void Upload(string fileName, bool resume, long lFileSize)
		{
			if ( !this.loggedin ) this.Login();

			Socket cSocket = null ;
			long offset = 0;
            Int64 iFileSize = 0;

			if ( resume )
			{
				try
				{
					this.BinaryMode = true;

					offset = GetFileSize( Path.GetFileName(fileName) );
				}
				catch(Exception)
				{
					// file not exist
					offset = 0;
				}
			}

            iFileSize = new System.IO.FileInfo(fileName).Length;
			// open stream to read file
			FileStream input = new FileStream(fileName,FileMode.Open, FileAccess.Read);

			if ( resume && input.Length < offset )
			{
				// different file size
				Debug.WriteLine("Overwriting " + fileName, "FtpClient");
				offset = 0;
			}
			else if ( resume && input.Length == offset )
			{
				// file done
				input.Close();
				Debug.WriteLine("Skipping completed " + fileName + " - turn resume off to not detect.", "FtpClient");
				return;
			}

			// dont create untill we know that we need it
			cSocket = this.createDataSocket();

			if ( offset > 0 )
			{
				this.sendCommand( "REST " + offset );
				if ( this.resultCode != 350 )
				{
					Debug.WriteLine("Resuming not supported", "FtpClient");
					offset = 0;
				}
			}

			this.sendCommand( "STOR " + Path.GetFileName(fileName) );

			if ( this.resultCode != 125 && this.resultCode != 150 ) throw new FtpException(result.Substring(4));

			if ( offset != 0 )
			{
				Debug.WriteLine("Resuming at offset " + offset, "FtpClient" );

				input.Seek(offset,SeekOrigin.Begin);
			}

			Debug.WriteLine( "Uploading file " + fileName + " to " + remotePath, "FtpClient" );

            Int64 iCountBytes = 0;
			while ((bytes = input.Read(buffer,0,buffer.Length)) > 0)
			{
				cSocket.Send(buffer, bytes, 0);

                iCountBytes += bytes;

                iProgress = iCountBytes * 100 / iFileSize;

                RaiseProgressChangedEvent();
			}
			
			input.Close();

			if (cSocket.Connected)
			{
				cSocket.Close();
			}

			this.readResponse();

			if( this.resultCode != 226 && this.resultCode != 250 ) throw new FtpException(this.result.Substring(4));
		}


        /// <summary>
        /// Upload a file and set the resume flag.
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="resume"></param>
        public void Upload(string fileName, string targetName, bool resume, long lFileSize)
        {
            if (!this.loggedin) this.Login();

            Socket cSocket = null;
            long offset = 0;

            this.iProgress = 0;

            CancelFiletransfer = false;
           
            if (resume)
            {
                try
                {
                    this.BinaryMode = true;

                    offset = GetFileSize(Path.GetFileName(fileName));
                }
                catch (Exception)
                {
                    // file not exist
                    offset = 0;
                }
            }

            // open stream to read file
            FileStream input = new FileStream(fileName, FileMode.Open);

            if (resume && input.Length < offset)
            {
                // different file size
                Debug.WriteLine("Overwriting " + fileName, "FtpClient");
                offset = 0;
            }
            else if (resume && input.Length == offset)
            {
                // file done
                input.Close();
                Debug.WriteLine("Skipping completed " + fileName + " - turn resume off to not detect.", "FtpClient");
                return;
            }

            // dont create untill we know that we need it
            cSocket = this.createDataSocket();

            if (offset > 0)
            {
                this.sendCommand("REST " + offset);
                if (this.resultCode != 350)
                {
                    Debug.WriteLine("Resuming not supported", "FtpClient");
                    offset = 0;
                }
            }

            this.sendCommand("STOR " + targetName);

            if (this.resultCode != 125 && this.resultCode != 150)
            {
                cSocket.Close();
                throw new FtpException(result.Substring(4));
            }

            if (offset != 0)
            {
                Debug.WriteLine("Resuming at offset " + offset, "FtpClient");

                input.Seek(offset, SeekOrigin.Begin);
            }

            Debug.WriteLine("Uploading file " + targetName + " to " + remotePath, "FtpClient");

            DateTime dtStart = DateTime.Now;

            int bytes = input.Read(buffer, 0, buffer.Length);
            iBytesDownloaded = offset + bytes;

            while (bytes > 0)
            {
                if (CancelFiletransfer)
                {
                    cSocket.Close();

                    input.Close();

                    return;
                }
                cSocket.Send(buffer, bytes, 0);

                bytes = input.Read(buffer, 0, buffer.Length);
                iBytesDownloaded += bytes;
            }

            input.Close();

            if (cSocket.Connected)
            {
                cSocket.Close();
            }

            if (this.resultCode != 226)
            {
                this.readResponse();
            }

                if (this.resultCode != 226 && this.resultCode != 250) throw new FtpException(this.result.Substring(4));
        }
        
        /// <summary>
		/// Upload a directory and its file contents
		/// </summary>
		/// <param name="path"></param>
		/// <param name="recurse">Whether to recurse sub directories</param>
		public void UploadDirectory(string path, bool recurse)
		{
			this.UploadDirectory(path,recurse,"*.*");
		}
		
		/// <summary>
		/// Upload a directory and its file contents
		/// </summary>
		/// <param name="path"></param>
		/// <param name="recurse">Whether to recurse sub directories</param>
		/// <param name="mask">Only upload files of the given mask - everything is '*.*'</param>
		public void UploadDirectory(string path, bool recurse, string mask)
		{
			string[] dirs = path.Replace("/",@"\").Split('\\');
			string rootDir = this.RemotePath + "/" + dirs[ dirs.Length - 1 ];

			// make the root dir if it doed not exist
			if ( this.GetFileList(rootDir).Length < 1 ) this.MakeDir(rootDir);

			this.ChangeDir(rootDir);

			foreach ( string file in Directory.GetFiles(path,mask) )
			{
				this.Upload(file,false,0);
			}
			if ( recurse )
			{
				foreach ( string directory in Directory.GetDirectories(path) )
				{
					this.UploadDirectory(directory,recurse,mask);
				}
			}

			this.ChangeDir(rootDir.Substring(0, rootDir.LastIndexOf('/')));
		}

		/// <summary>
		/// Delete a file from the remote FTP server.
		/// </summary>
		/// <param name="fileName"></param>
		public void DeleteFile(string fileName)
		{
			if ( !this.loggedin ) this.Login();

			this.sendCommand( "DELE " + fileName );

			if ( this.resultCode != 250 ) throw new FtpException(this.result.Substring(4));

			Debug.WriteLine( "Deleted file " + fileName, "FtpClient" );
		}

		/// <summary>
		/// Rename a file on the remote FTP server.
		/// </summary>
		/// <param name="oldFileName"></param>
		/// <param name="newFileName"></param>
		/// <param name="overwrite">setting to false will throw exception if it exists</param>
		public void RenameFile(string oldFileName,string newFileName, bool overwrite)
		{
			if ( !this.loggedin ) this.Login();

			this.sendCommand( "RNFR " + oldFileName );

			if ( this.resultCode != 350 ) throw new FtpException(this.result.Substring(4));

			if ( !overwrite && this.GetFileList(newFileName).Length > 0 ) throw new FtpException("File already exists");

			this.sendCommand( "RNTO " + newFileName );

			if ( this.resultCode != 250 ) throw new FtpException(this.result.Substring(4));

			Debug.WriteLine( "Renamed file " + oldFileName + " to " + newFileName, "FtpClient" );
		}
		
		/// <summary>
		/// Create a directory on the remote FTP server.
		/// </summary>
		/// <param name="dirName"></param>
		public void MakeDir(string dirName)
		{
			if ( !this.loggedin ) this.Login();

			this.sendCommand( "MKD " + dirName );

			if ( this.resultCode != 250 && this.resultCode != 257 ) throw new FtpException(this.result.Substring(4));

			Debug.WriteLine( "Created directory " + dirName, "FtpClient" );
		}

		/// <summary>
		/// Delete a directory on the remote FTP server.
		/// </summary>
		/// <param name="dirName"></param>
		public void RemoveDir(string dirName)
		{
			if ( !this.loggedin ) this.Login();

			this.sendCommand( "RMD " + dirName );

            if ( this.resultCode != 250 ) throw new FtpException(this.result.Substring(4));

			Debug.WriteLine( "Removed directory " + dirName, "FtpClient" );
		}

		/// <summary>
		/// Change the current working directory on the remote FTP server.
		/// </summary>
		/// <param name="dirName"></param>
		public void ChangeDir(string dirName)
		{
			if( dirName == null || dirName.Equals(".") || dirName.Length == 0 )
			{
				return;
			}

			if ( !this.loggedin ) this.Login();

			this.sendCommand( "CWD " + dirName );

			if ( this.resultCode != 250 ) throw new FtpException(result.Substring(4));

            this.sendCommand( "PWD" );

			//if ( this.resultCode != 257 ) throw new FtpException(result.Substring(4));

			// gonna have to do better than this....
            if (this.result.Contains("\""))
                this.remotePath = this.result.Split('"')[1];

            //this.RemotePath = dirName;
            Debug.WriteLine("Current directory is " + result, "FtpClient");
		}

		/// <summary>
		/// 
		/// </summary>
		private void readResponse()
		{
			this.message = "";
			this.result = this.readLine();

			if ( this.result.Length > 3 )
                try
                {
                    this.resultCode = int.Parse(this.result.Substring(0, 3));
                }
                catch
                { this.result = null; }
			else
				this.result = null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private string readLine()
		{
            try
            {
                while (true)
                {
                    this.bytes = clientSocket.Receive(this.buffer, this.buffer.Length, 0);
                    this.message += ASCII.GetString(this.buffer, 0, this.bytes);

                    if (this.bytes < this.buffer.Length)
                    {
                        break;
                    }
                }

                if (this.message != "")
                    RaiseRespondEvent();
                else
                    return "";

                string[] msg = this.message.Split(new char[] { '\r', '\n' });

                /*if (msg.Length > 2)
                {
                    if (msg[msg.Length - 1].Trim() == String.Empty)
                        this.message = msg[msg.Length - 2];
                    else
                        this.message = msg[msg.Length - 1];
                }
                else
                    this.message = msg[0];
                */
                string ret = "";
                foreach (string s in msg)
                {
                    int i = 0;
                    if (s.Length > 4 && s.Substring(3, 1) == " "
                        && int.TryParse(s.Substring(0, 3), out i))
                        ret = s;
                }

                if (ret != "")
                    return ret;

                if (this.verboseDebugging)
                {
                    for (int i = 0; i < msg.Length - 1; i++)
                    {
                        Debug.Write(msg[i], "FtpClient");
                    }
                }

                return this.readLine();
            }
            catch (Exception ex)
            {
                clientSocket.Close();
                this.loggedin = false;
                throw new FtpException(ex.Message);
            }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="command"></param>
		private void sendCommand(String command)
		{
            if (clientSocket == null || !clientSocket.Connected)
            {
                this.loggedin = false;

                if (!command.Contains("QUIT"))
                {
                    this.message = "Verbindung verloren. Starte neue Verbindung";

                    RaiseRespondEvent();

                    Login();
                }

                return;
            }

            if (clientSocket.Available > 0)
            {
                this.readResponse();

                if (this.resultCode == 421)
                {
                    this.loggedin = false;

                    if (!command.Contains("QUIT"))
                    {
                        this.message = "Verbindung verloren. Starte neue Verbindung";

                        RaiseRespondEvent();

                        Login();
                    }
                }
            }

            if (command.Contains("PASS"))
                this.message = "SENDING: PASS ********";
            else
                this.message = "SENDING: " + command;

            RaiseRespondEvent();

			if ( this.verboseDebugging ) Debug.WriteLine(command,"FtpClient");
            Byte[] cmdBytes = ASCII.GetBytes( ( command + "\r\n" ).ToCharArray() );
			clientSocket.Send( cmdBytes, cmdBytes.Length, 0);

			this.readResponse();
		}

		/// <summary>
		/// when doing data transfers, we need to open another socket for it.
		/// </summary>
		/// <returns>Connected socket</returns>
		private Socket createDataSocket()
		{
            string ipAddress = "";

            int port = -1;

            Socket socket = null;
            IPEndPoint ep = null;

            switch(this.clientSocket.AddressFamily)
            {
                case AddressFamily.InterNetwork:
                    this.sendCommand("PASV");

			        if ( this.resultCode != 227 ) throw new FtpException(this.result.Substring(4));

                    int index1 = this.result.IndexOf('(');
			        int index2 = this.result.IndexOf(')');

                    if (index2 == -1)
                        index2 = this.result.Length;

			        string ipData = this.result.Substring(index1+1,index2-index1-1);

			        int[] parts = new int[6];

			        int len = ipData.Length;
			        int partCount = 0;
			        string buf="";

			        for (int i = 0; i < len && partCount <= 6; i++)
			        {
				        char ch = char.Parse( ipData.Substring(i,1) );

				        if ( char.IsDigit(ch) )
					        buf+=ch;

				        else if (ch != ',')
					        throw new FtpException("Malformed PASV result: " + result);

				        if ( ch == ',' || i+1 == len )
				        {
					        try
					        {
						        parts[partCount++] = int.Parse(buf);
						        buf = "";
					        }
					        catch (Exception ex)
					        {
						        throw new FtpException("Malformed PASV result (not supported?): " + this.result, ex);
					        }
				        }
			        }

			        ipAddress = parts[0] + "."+ parts[1]+ "." + parts[2] + "." + parts[3];

			        port = (parts[4] << 8) + parts[5];

                    socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
				    // ep = new IPEndPoint(Dns.Resolve(ipAddress).AddressList[0], port);
                    IPAddress ip = IPAddress.Parse(ipAddress);

                    ep = new IPEndPoint(ip, port);
                    break;
                case AddressFamily.InterNetworkV6:
                    this.sendCommand("EPSV");
                    //229 Entering Extended Passive Mode (|||63390|)

			        if ( this.resultCode != 229 ) throw new FtpException(this.result.Substring(4));

                    string[] aParts = this.result.Split('|');

                    if(aParts.Length != 5 || !int.TryParse(aParts[3], out port))
                        throw new FtpException("Malformed EPSV result (not supported?): " + this.result, null);

                    socket = new Socket(this.clientSocket.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                    IPAddress ipV6 = IPAddress.Parse(this.remoteIP);

                    ep = new IPEndPoint(ipV6, port);
                    break;
                default:
                    throw new FtpException("Unsupportet Network Protokoll!", null);
                    break;
            }

			try
			{
				socket.Ttl = 42;

                socket.ReceiveBufferSize = 8192;
                socket.SendBufferSize = 8192;

                socket.NoDelay = true;

                socket.ReceiveTimeout = this.timeoutSeconds * 1000;
                socket.SendTimeout = this.timeoutSeconds * 1000;

				socket.Connect(ep);
			}
			catch(Exception ex)
			{
				// doubtfull....
				if ( socket != null && socket.Connected ) socket.Close();

				throw new FtpException("Can't connect to remote server: " + ipAddress + ":" + port, ex);
			}

			return socket;
		}
		
		/// <summary>
		/// Always release those sockets.
		/// </summary>
		private void cleanup()
		{
			if ( this.clientSocket!=null )
			{
				this.clientSocket.Close();
				this.clientSocket = null;
			}
			this.loggedin = false;
		}

        //Implement IDisposable.
        public void Dispose() 
        {
            Dispose(true);
            GC.SuppressFinalize(this); 
        }

        protected virtual void Dispose(bool disposing) 
        {
          if (disposing) 
          {
              this.cleanup();
          }
          // Free your own state (unmanaged objects).
          // Set large fields to null.
        }

        // Use C# destructor syntax for finalization code.
        ~FtpClient()
        {
          // Simply call Dispose(false).
          Dispose (false);
        }      
	}

    public class RespondEventArgs : EventArgs
    {
        public RespondEventArgs(string sRespond)
        {
            this.sRespond = sRespond;
        }

        private string sRespond = "";

        public string Respond
        {
            get { return sRespond;  }
        }
    }

    public class ProgressChangedEventArgs : EventArgs
    {
        public ProgressChangedEventArgs(int iProgress, Int64 iBytesDownloaded)
        {
            this.iProgress = iProgress;
            this.iBytesDownloaded = iBytesDownloaded;
        }

        private int iProgress = 0;
        private Int64 iBytesDownloaded = 0;

        public int Progress
        {
            get {
                if (iProgress > 100) 
                    return 100;
                else
                    return iProgress; 
            }
        }

        public Int64 BytesDownloaded
        {
            get
            {
                return iBytesDownloaded;
            }
        }
    }
}